package p2p;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.lang.reflect.Array;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.CharBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;

import jhttpp2.Jhttpp2Read;
import jhttpp2.Jhttpp2Server;

public class ControlHandler extends Thread {
	
	private static final int REQUEST_URL_FROM_LOCAL = 0;
	private static final int REQUEST_URL = 1;
	private static final int NEED_URL = 2;
	private static final int PING = 3;
	
	public static String myUuid = "";
	
	ServerSocket servSock;
    int threadNumber;
    Socket clientSocket;
    
    public static String P2PControlHost = "127.0.0.1";
    public static int p2PControlPort = 9999;
    
	ControlHandler(ServerSocket s, int i) {
        super();
        //read config server p2p control
        servSock = s;
        threadNumber = i;
        setName("Thread " + threadNumber + " Port: " + String.valueOf(P2pClientThread.ECHOPORT));
    }
	
	public ControlHandler( Socket sk ) {
		// TODO Auto-generated constructor stub
		clientSocket = sk;
	}
	
	public void run() {
        /* Wait for a connection */
//		System.out.println("My UUID:" + myUuid);
		
        //while (true){
            try {
//				System.out.println( getName(  ) + " waiting");
//				Socket clientSocket;
//				
//				// Wait here for the next connection.
//				synchronized(servSock) {
//				     clientSocket = servSock.accept();
//				}
				
				int command = parseCommand(clientSocket);
//				System.out.println(command);
  
				switch (command) {
				case REQUEST_URL_FROM_LOCAL:
					progressRequestUrlFromLocal(clientSocket);
					break;
				case REQUEST_URL:
					progressRequestUrl(clientSocket);
					break;
				case NEED_URL:
					progressNeedUrl(clientSocket);
					break;
				case PING:
					progressPing(clientSocket);
					break;
				default:
					break;
				}
			    
				if( command != PING) {
					System.out.println(getName(  ) + " ENDED ");
//					clientSocket.shutdownOutput();
					clientSocket.close(  );
				}
            } catch (IOException ex) {
			    System.out.println(getName(  ) + ": IO Error on socket " + ex);
			    return;
			}
        //}
    }

	private void progressPing(Socket clientSocket) {
		// TODO Auto-generated method stub
		try {
//			System.out.println("Ping request");
			PrintWriter os = new PrintWriter(clientSocket.getOutputStream( ), true);
			os.print("ONLINE\r\n");
			os.flush();
//			System.out.println("Ping response");
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}

	private void progressNeedUrl(Socket clientSocket) {
		// TODO Auto-generated method stub
		try {
			BufferedReader is = new BufferedReader(new InputStreamReader(
					clientSocket.getInputStream()));
			PrintWriter os = new PrintWriter(clientSocket.getOutputStream( ), true);
			
			String rurl = is.readLine();
			if( rurl == null )
				return;
			
			String rfriendUUID = is.readLine();
			
			int indexUrl = rurl.indexOf("URL=");
			String url = rurl.substring(indexUrl + 4);
			
			int indexUUID = rfriendUUID.indexOf("UUID=");
			String friendUUID = rfriendUUID.substring(indexUUID + 5);
			
			System.out.println("NEED_URL: need request from:" + friendUUID );
			
			System.out.println("NEED: NEED? " + url);
			String hashurl = Jhttpp2Read.hashString(url);
			
			if( Jhttpp2Read.lockUrl.containsKey(hashurl) ) {
				System.out.println("NEED:NOT NEED RES");
				os.print("NOT_NEED\r\n");
				os.flush();
				os.close();
			} else if ( P2pClientThread.urlwait.containsKey(hashurl) ) {
				Jhttpp2Read.lockUrl.put(hashurl, 1);
				P2pClientThread.urlwriting.put(hashurl, 1);
				
				System.out.println("NEED:NEED RES");
				os.print("NEED\r\n");
				os.flush();
				
				Jhttpp2Server.p2pCache.print(url + "\r\n");
				Jhttpp2Server.p2pCache.flush();
				
				FileOutputStream fos = new FileOutputStream( new File("data/" +/* "NEED" +*/  hashurl + ".cache"));
				InputStream fin = clientSocket.getInputStream();
				int bytes_read = 0;
				byte[] buf = new byte[Jhttpp2Read.BUFFER_SIZE];
				System.out.println("NEED:Open stream:" + hashurl);
				while(true) {
					bytes_read=fin.read(buf);
					System.out.println(bytes_read);
		    		if (bytes_read != -1) {	
			    			fos.write(buf);
			    			fos.flush();
//			    			System.out.println(bytes_read);
		    		} else break;
		    	}
				
				fos.flush();
				fos.close();
//				fin.close();
				
//				for(int iii = 0; iii < 10000; iii++);
				
				P2pClientThread.urlwriting.remove(hashurl);
				Jhttpp2Read.lockUrl.remove(hashurl);
				P2pClientThread.urlwait.remove(hashurl);
			} else {
				System.out.println("NEED:NOT NEED RES");
				os.print("NOT_NEED\r\n");
				os.flush();
				os.close();
			}
			
			
			//add count hit one value
			if( !P2pClientThread.peerhm.containsKey(friendUUID) ) {
				PeerInfo newpeerinfo = new PeerInfo();
				newpeerinfo.uuid = friendUUID;
				newpeerinfo.ip = clientSocket.getInetAddress().getHostAddress().toString();
				newpeerinfo.port = String.valueOf(clientSocket.getPort());
				
				System.out.println("NEED:ip:" + newpeerinfo.ip + "port:" + newpeerinfo.port);
				newpeerinfo.hitCount++;
				
				P2pClientThread.peerhm.put(friendUUID, newpeerinfo);
			} else {
				PeerInfo peerinfo = P2pClientThread.peerhm.get(friendUUID);
				peerinfo.hitCount++;
				
				System.out.println("NEED:ip:" + peerinfo.ip + "port:" + peerinfo.port);
			}
			
			P2PautoRemoveFriend.totalHitCount++;
			
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}

	private void progressRequestUrl(Socket clientSocket) {
		// TODO Auto-generated method stub
		BufferedReader is;
		try {
			is = new BufferedReader(new InputStreamReader(
					clientSocket.getInputStream()));
			String ruuid = is.readLine();
			String rip = is.readLine();
			String rport = is.readLine();
			String rurl = is.readLine();
			String rcount = is.readLine();
			String rforward = is.readLine();
			String rforwardPeerInfoString = is.readLine();
			
			int ruuidIndex = ruuid.indexOf("UUID=");
			int ripIndex = rip.indexOf("IP=");
			int rportIndex = rport.indexOf("PORT=");
			int rurlIndex = rurl.indexOf("URL=");
			int rcountIndex = rcount.indexOf("COUNT=");
			int rforwardIndex = rforward.indexOf("FORWARD=");
			int rforwardhostinfoIndex = rforwardPeerInfoString.indexOf("FORWARD_INFO=");
			
			String uuid = ruuid.substring(ruuidIndex + 5);
//			System.out.println("REQUET_URL From: " + uuid);
			//new ma duoc forward tu minh thi break luon
			if( uuid == myUuid )
				return;
			
			String ip = rip.substring(ripIndex + 3);
			String port = rport.substring(rportIndex + 5);
			String url = rurl.substring(rurlIndex + 4);
			String count = rcount.substring(rcountIndex + 6);
			String forward = rforward.substring(rforwardIndex + 8);
			String forwardPeerInfoString = rforwardPeerInfoString.substring(rforwardhostinfoIndex + 13);
			
//			System.out.println("FORWARD_INFO=" + forwardPeerInfoString);
			
//			System.out.println(ip);
//			System.out.println(port);
//			System.out.println(url);
//			System.out.println(count);
//			System.out.println(forward);
			
			//create list forwarded array list
			String[] splitForward = forward.split(",");
			List<String> listForward = Arrays.asList(splitForward);
			System.out.println("FORWARD:");
			for(int i = 0; i < splitForward.length; i++ ) 
				System.out.println(splitForward[i]);
			
			//if have cache run NEED protocol
			if( Jhttpp2Read.searchExistCache(url) > 0 ) {
				System.out.println("REQUEST_URL:P2P we have cache of: " + url);
				Socket requestSock = new Socket(ip, Integer.parseInt(port));
				PrintWriter ros = new PrintWriter(requestSock.getOutputStream( ), true);
				BufferedReader ris = new BufferedReader(new InputStreamReader(requestSock.getInputStream()));
				
				ros.print("NEED_URL\r\n");
				ros.flush();
				ros.print("URL=" + url + "\r\n");
				ros.flush();
				ros.print("UUID=" + myUuid + "\r\n");
				ros.flush();
				
				String needReply = ris.readLine();		
				System.out.println("REQUEST_URL:reply:" + needReply);
				//write data to request client
				if( needReply.equals("NEED") ) {
					String hashStr = Jhttpp2Read.hashString(url);
					File file = new File("data/" + hashStr + ".cache");
					FileInputStream fiis = new FileInputStream(file);
					
					OutputStream rsos = requestSock.getOutputStream();
					byte[] cbuf = new byte[Jhttpp2Read.BUFFER_SIZE];
					while( true ){
						int check = fiis.read(cbuf);
						if( check < 0 ) break;
						System.out.println(check);
						
						rsos.write(cbuf);
						rsos.flush();
					}
					
					fiis.close();
//					ros.close();
				}
				
//				for(int iii = 0; iii < 10000; iii++) {
//					ros.flush();
//				}
				
				requestSock.shutdownOutput();
//				requestSock.close();
				return;
			} else {
				//if not have cache run this code to forward request to friend
				System.out.println("REQUEST_URL:P2P we don`t have cache of: " + url);
				int countInt = Integer.parseInt(count);
				
				Random generator = new Random();
				Object[] values = P2pClientThread.peerhm.values().toArray();
				ArrayList<Integer> arrInt = new ArrayList<Integer>();
				int loop;
				
				if( countInt > P2pClientThread.peerhm.size() )
					loop = P2pClientThread.peerhm.size();
				else 
					loop = countInt;
				
				
				// forward request url to peer, with maximum number of peer i can is 6 for friend size
				for ( int i = 0; i < loop; i++ ) {
//					System.out.println(i);
					int index;
					PeerInfo randomValue;
					//sinh ra mot id khong co
					do {
						index = generator.nextInt(values.length);
//						System.out.println("INDEX:" + index);
					}
					while( arrInt.indexOf(index) != -1);
					
					randomValue = (PeerInfo) values[index];
//					System.out.println(randomValue.uuid);
//					System.out.println(randomValue.ip);
					if( listForward.contains(randomValue.uuid) ) 
						continue;
					
//					System.out.println(randomValue.ip);
					
					//enable flag of index
					arrInt.add(index);
//					System.out.println(arrInt.indexOf(index));
					
					Socket forwardSock = new Socket(randomValue.ip, Integer.parseInt(randomValue.port));
					PrintWriter os = new PrintWriter(forwardSock.getOutputStream( ), true);
					String mesg = "REQUEST_URL";
					os.print( mesg + "\r\n");
					os.flush();
					os.print( "UUID=" + uuid + "\r\n");
					os.flush();
					os.print( "IP=" + ip + "\r\n");
					os.flush();
					os.print( "PORT=" + port + "\r\n");
					os.flush();
					os.print( "URL=" + url + "\r\n");
					os.flush();
					os.print( "COUNT=" + String.valueOf(countInt - 1) +"\r\n");
					os.flush();
					
					String newForwardStr = "";
					for(int i2 = 0; i2 < listForward.size(); i2++ ) {
						if( i2 == 0 )
							newForwardStr += listForward.get(i2);
						else
							newForwardStr += "," + listForward.get(i2);
					}
					if( !listForward.contains(myUuid))
						newForwardStr += "," + myUuid;
					os.print( "FORWARD=" + newForwardStr +"\r\n");
					os.flush();
					
					os.print( "FORWARD_INFO="+ myUuid + "," + P2pClientThread.ECHOPORT + "\r\n");
					os.flush();
					
					System.out.println("REQUEST_URL:Send friend:" + randomValue.uuid);
					
//					String reply = is.readLine( );
//		            System.out.println("Got  \"" + reply + "\"");
//					forwardSock.shutdownOutput();
//					forwardSock.close();
				}
			}
			
			//forward info for forward peer
			String[] forwardPeerInfo = forwardPeerInfoString.split(",");
			//add new friend
			String clientIp = clientSocket.getInetAddress().getHostAddress().toString();
			
			if( !P2pClientThread.peerhm.containsKey(forwardPeerInfo[0]) ) {
				PeerInfo newpeerinfo = new PeerInfo();
				newpeerinfo.uuid = forwardPeerInfo[0];
				newpeerinfo.ip = clientIp;
				newpeerinfo.port = forwardPeerInfo[1];
				
				P2pClientThread.peerhm.put(forwardPeerInfo[0], newpeerinfo);
			}
			
			return;
			
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}

	private void progressRequestUrlFromLocal(Socket clientSocket) {
		// TODO Auto-generated method stub
		BufferedReader is;
		try {
			is = new BufferedReader(new InputStreamReader(
					clientSocket.getInputStream()));
			PrintWriter rqos = new PrintWriter(clientSocket.getOutputStream( ), true);
			
			
			String rurl = is.readLine();
			System.out.println("URL_LOCAL: read url:" + rurl);
			if( rurl == null ) {
				System.out.println("URL_LOCAL:No URL Sent");
				return;
			}
			
//			rqos.print("READED REQUEST URL\r\n");
//			rqos.flush();
			
			int rurlIndex = rurl.indexOf("URL=");
			
			String url = rurl.substring(rurlIndex + 4);
			System.out.println("URL_LOCAL:" + url);
			
			//wait url
			String hashurl = Jhttpp2Read.hashString(url);
			P2pClientThread.urlwait.put(hashurl, 1);
			
			//if not have cache run this code to forward request to friend
			int countInt = 6;
			
			Random generator = new Random();
			Object[] values = P2pClientThread.peerhm.values().toArray();
			ArrayList<Integer> arrInt = new ArrayList<Integer>();
			int loop;
			
			if( countInt > P2pClientThread.peerhm.size() )
				loop = P2pClientThread.peerhm.size();
			else 
				loop = countInt;
			
//			System.out.println("LOOP: " + loop);
			
			// forward request url to peer, with maximum number of peer i can is 6 for friend size
			for ( int i = 0; i < loop; i++ ) {
				int index;
				PeerInfo randomValue;
				//sinh ra mot id khong co
				do {
					index = generator.nextInt(values.length);
				}
				while( arrInt.indexOf(index) != -1);
				
				randomValue = (PeerInfo) values[index];
				if( randomValue.uuid == myUuid ) 
					continue;
				
				//enable flag of index
				arrInt.add(index);
//				System.out.println(arrInt.indexOf(index));
				
				Socket forwardSock = new Socket(randomValue.ip, Integer.parseInt(randomValue.port));
				PrintWriter os = new PrintWriter(forwardSock.getOutputStream( ), true);
				String mesg = "REQUEST_URL";
				os.print( mesg + "\r\n");
				os.flush();
				os.print( "UUID=" + myUuid + "\r\n");
				os.flush();
				os.print( "IP=" + Inet4Address.getLocalHost().getHostAddress().toString() + "\r\n");
				os.flush();
				os.print( "PORT=" + P2pClientThread.ECHOPORT + "\r\n");
				os.flush();
				os.print( "URL=" + url + "\r\n");
				os.flush();
				os.print( "COUNT=" + String.valueOf(countInt) +"\r\n");
				os.flush();
				
				String newForwardStr = myUuid;
				os.print( "FORWARD=" + newForwardStr +"\r\n");
				os.flush();
				
				os.print( "FORWARD_INFO="+ myUuid + "," + P2pClientThread.ECHOPORT + "\r\n");
				os.flush();
				
				System.out.println("URL_LOCAL:Send friend:" + randomValue.uuid);

			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}

	private int parseCommand(Socket clientSocket) {
		// TODO Auto-generated method stub
		BufferedReader is;
		try {
			is = new BufferedReader(new InputStreamReader(
					clientSocket.getInputStream()));	

	        String line = is.readLine();
	        System.out.println("READED: " + line);
    		if( line.equals("REQUEST_URL_FROM_LOCAL") )
            	return REQUEST_URL_FROM_LOCAL;
    		else if( line.equals("REQUEST_URL") ) {
    			return REQUEST_URL;
    		} else if( line.equals("NEED_URL") ) {
    			return NEED_URL;
    		}  if( line.equals("PING") ) {
    			return PING;
    		} else 
    			return -1;
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

        return -1;
	}
}
